home *** CD-ROM | disk | FTP | other *** search
/ Acorn User: China / Acorn User China CD-ROM (UK) (Disc B) / Acorn User China CD-ROM (UK) (Disc B).bin / STUTTGART / PROBLEMS / PREEMPTER / text0000.txt < prev   
Encoding:
Text File  |  1992-04-14  |  16.7 KB  |  631 lines

  1.  
  2. In article <1992Apr13.183936.3762@news.uni-stuttgart.de> you wrote:
  3.  
  4. >Hi
  5. >
  6. >A few weeks ago, someone told here, that he wrote a tool for preemptive
  7. >multitasking and will public it in comp.binaries.acorn.
  8. >Unfortunatly comp.binaries.acorn is closed for a while. Could someone 
  9. >give me informations about the author ?
  10.  
  11. It's included here...
  12.  
  13. >thanks
  14. >so long
  15. >MUFTI
  16. >
  17. >ps:
  18. >    Shouldn't the moderator of comp.binaries.acorn lay a forward to 
  19. >    one of the ftp/mail-servers, if he isn't on duty for a long time ?
  20. >    The software he gets is not for private Acorn ltd. use .....
  21.  
  22. We could forward all stuff to Albert at Newcastle. The reasons that 
  23. nothing has been posted for a while are as follows:-
  24.  
  25.         a) Phil Colmer is ill at present; he would normally handle
  26.            comp.sources.acorn
  27.  
  28.         b) Alan Glover and myself are v. busy right now. I expect to
  29.            begin posting things again soon, maybe withing the next week
  30.            or two. As for Alan, I don't know. Unfortunately, moderating
  31.            comp.*.acorn isn't really seen as part as the 'job' that we
  32.            do here, so it has to be a spare time activity. 
  33.  
  34. Chris.
  35.  
  36. ______________________________________________________________________
  37. Chris Marshall                                   cmarshall@acorn.co.uk
  38.  
  39. >From pc123@phx.cam.ac.uk Tue Feb 25 19:29:59 1992
  40. Received: by oak.acorn.co.uk (4.1/Ai1.6)
  41.     id AA11229; Tue, 25 Feb 92 19:29:56 GMT
  42. Received: by acorn.co.uk (4.1/Am32)
  43.     id AA13477; Tue, 25 Feb 92 19:29:02 GMT
  44. Received: from phx.cam.ac.uk by eros.uknet.ac.uk via JANET with NIFTP (PP) 
  45.           id <25975-0@eros.uknet.ac.uk>; Tue, 25 Feb 1992 18:22:59 +0000
  46. Date: Tue, 25 Feb 92 18:22:08 GMT
  47. >From: Pete <pc123@phx.cam.ac.uk>
  48. To: submit@acorn.co.uk
  49. Subject: Code for pre-empting a non-desktop task
  50. Message-Id: <A54D6B55FDE90040@UK.AC.CAMBRIDGE.PHOENIX>
  51.  
  52. Newsgroups: comp.sources.acorn
  53.  
  54. Here is the code to demonstrate how you can pre-empt a non-desktop program
  55. without running it in a taskwindow or under FrontEnd, as I promised on c-s-a
  56. recently.  The code implements a module which acts a bit like FrontEnd but is a
  57. tinsy bit more efficient in its use of RMA; it should be possible to convert it
  58. for multitasking raytracers or other code you are writing without too much
  59. difficulty.
  60.  
  61. The module writes all output generated by the program into a circular buffer at
  62. a fixed offset in its workspace - a desktop task then uses the OS_Module call
  63. to find out where the workspace is, and reads data out.  The effect is that
  64. text produced by a program is not stored in the RMA, so excessive fragmentation
  65. does not occur.  This also implies that this module is only the "business end"
  66. of a complete FrontEnd replacement, and you have to write a desktop client
  67. program as well, to display output produced by a task in a window.  I am not
  68. posting the client here, because this source is really for illustration only.
  69.  
  70. The module also allows the task it is running to be stopped and restarted under
  71. control of the controlling program.  This is accomplished by writing various
  72. values into another word at a fixed offset in the module's workspace.
  73. Similarly the module may be asked to kill the program it is pre-empting (never
  74. scheduling it again, and closing all its files) or terminate it (call its exit
  75. handler).
  76.  
  77. As I wrote Express Assembler, the module is written in Express format.  You
  78. shouldn't have too much trouble understanding it, as it does not use any fancy
  79. features.
  80.  
  81. It is well known that only Quiche Eaters comment their programs well.  I have
  82. added a few, but...
  83.  
  84. ...if you find you need help following it, send me mail and I will do my best
  85. to help.  Or flame about it on c-s-a instead  :-)  .
  86.  
  87.  
  88.         SETTYPE "MODULE"
  89.         OBJECT "!YAMU.Initiator"
  90.         ORG 0
  91.         ASSUME PC,0
  92.         ASSUME R12,&10000000
  93.  
  94.         SET VSTR,"1.00"
  95.  
  96.         DD LANGUAGE,0,0,SERVICE,TITLE,HELP,0,0,0,0,0
  97.  
  98. TITLE   DB "Initiator",0
  99.         ALIGN
  100.  
  101. HELP    DB "Initiator",9,"`VSTR` (",OSVAR "Sys$Date" RIGHT 6," ",OSVAR "Sys$Year",")",0
  102.         ALIGN
  103.  
  104. TASKDESC
  105.         DB "Init",0
  106.         ALIGN
  107.  
  108. LANGUAGE
  109.         LOCALS
  110.         MOV R11,R0
  111.  
  112.         ; Claim memory
  113.  
  114.         MOV R0,#6
  115.         LIT R3,WEND-WSTART
  116.         SWI "OS_Module"
  117.         STR R2,[R12]
  118.         STR R12,[R2]
  119.         MOV R12,R2
  120.  
  121.         ; Initialise variables
  122.  
  123.         MOV R0,#0
  124.         ST R0,MEMORYMAPVETOED
  125.         ST R0,OUTBUFFERREAD
  126.         ST R0,OUTBUFFERWRITE
  127.         ST R0,MESSAGES
  128.         ST R0,SUSPENDED
  129.         ST R0,TERMINATING
  130.         ST R0,HANDLESUSED
  131.  
  132.         ; Register as Wimp task
  133.  
  134.         MOV R0,#200
  135.         LIT R1,&4B534154
  136.         ADR R2,TASKDESC
  137.         SWI "Wimp_Initialise"
  138.         ST R1,TASKHANDLE
  139.  
  140.         ; Calculate appropriate (initial) slot size.  This is not calculated
  141.         ; in any special way, it just tries to allocate a reasonable amount
  142.         ; of space.  The newer applications ask the Wimp for more space if
  143.         ; they run out.
  144.  
  145.         MVN R0,#0
  146.         MVN R1,#0
  147.         SWI "Wimp_SlotSize"
  148.         ADD R0,R0,R1
  149.         ADD R0,R0,R2
  150.         CMP R0,#512*1024
  151.         MOVGE R0,#512*1024
  152.         SUBLT R0,R0,#64*1024
  153.         CMP R0,#0
  154.         MOVLE R0,#16*1024
  155.         MVN R1,#0
  156.         SWI "Wimp_SlotSize"
  157.  
  158.         ; Initialise environment, so that funny things don't happen when the
  159.         ; background task calls the exit handler, etc.  In fact the exit
  160.         ; handler has to come back into this code in order for us to tidy up.
  161.  
  162.         MOV R0,#6 ; Error handler
  163.         ADR R1,ERRORHANDLER
  164.         MOV R2,R12
  165.         ADR R3,ERRORBUFFER
  166.         SWI "OS_ChangeEnvironment"
  167.         ADR R0,OLDERRORHANDLER
  168.         STMIA R0,{R1-R3}
  169.  
  170.         MOV R0,#11 ; Exit handler
  171.         ADR R1,EXITHANDLER
  172.         MOV R2,R12
  173.         MOV R3,#0
  174.         SWI "OS_ChangeEnvironment"
  175.         ADR R0,OLDEXITHANDLER
  176.         STMIA R0,{R1-R3}
  177.  
  178.         ; Set interruption of background task
  179.  
  180.         BL SETCALLAFTER
  181.         BL CLAIMVECS
  182.  
  183.         ; And start the task off
  184.  
  185.         MOV R0,R11
  186.         SWI "OS_Write0"         ; This just writes the name of the command
  187.         SWI "OS_NewLine"        ; run into the output buffer.
  188.         MOV R0,R11
  189.         SWI "OS_CLI"            ; Execute the * command that runs the task.
  190.  
  191.         ; This may return, if no application is started.  In this case, there
  192.         ; is an outstanding ticker event to cancel, but apart from that we
  193.         ; just exit.
  194.  
  195.         ADR R13,USRSTACK
  196.         BL CANCELTICKER         ; Cancel the descheduling time-out.
  197.         BL RELEASEVECS          ; Release vectors we have claimed.
  198.         B KILLMODULE
  199.  
  200. MAINLOOP
  201.         BL RELEASEVECS          ; Must release all vectors before polling.
  202. A
  203.         MOV R0,#0
  204.         ADR R1,POLLBLOCK
  205.         SWI "Wimp_Poll"
  206.         TEQ R0,#0
  207.         BLNE NONIDLEPOLL
  208.         ADR R13,USRSTACK
  209.         BL RECEIVEMESSAGES
  210.         BNE A
  211.  
  212.         ; See whether the output buffer is empty - if not we wait until it
  213.         ; has been emptied before running the task again.
  214.  
  215.         LD R0,OUTBUFFERREAD
  216.         LD R1,OUTBUFFERWRITE
  217.         TEQ R0,R1
  218.         BNE A
  219.  
  220.         ; If the task has been stopped by the controlling task, don't
  221.         ; reschedule it.
  222.  
  223.         LD R0,SUSPENDED
  224.         TEQ R0,#0
  225.         BNE A
  226.  
  227.         ; Set up conditions for re-entering task
  228.  
  229.         BL SETCALLAFTER         ; Create a new CallAfter event
  230.         BL CLAIMVECS            ; and claim the vectors again.
  231.  
  232.         ; If the task is being terminated, get ready to call OS_Exit
  233.  
  234.         LD R0,TERMINATING
  235.         TEQ R0,#0
  236.         MOV R0,#0
  237.         ST R0,TERMINATING
  238.  
  239.         ; Back into the task again.  When restoring the registers, note that
  240.         ; the correct method for doing this is very dependent on the current
  241.         ; processor mode - what is shown is NOT a general way of restoring
  242.         ; a register dump after an exception.
  243.  
  244.         ADR R0,REGBUF
  245.         LDMEQIA R0,{R0-PC}^     ; Restore the task's registers.
  246.         LDMIA R0,{R0-R14}
  247.         MOV R0,#0
  248.         LIT R1,&58454241
  249.         MOV R2,#2
  250.         SWI "OS_Exit"           ; Task is being terminated.  Note how we can
  251.                             ; even call OS_Exit "within" a CallBack handler.
  252.  
  253.         ; General rubbish to do with being a Wimp task, nothing amazing here.
  254.  
  255. NONIDLEPOLL
  256.         TEQ R0,#17
  257.         TEQNE R0,#18
  258.         MOVNES PC,R14
  259.         LD R0,POLLBLOCK+16
  260.         TEQ R0,#0 ; Message_Quit
  261.         MOVNES PC,R14
  262.         ADR R13,USRSTACK
  263.         BL CANCELTICKER
  264.         MOV R0,#0
  265.         ST R0,OUTBUFFERREAD
  266.         ST R0,OUTBUFFERWRITE
  267.  
  268.         ; and continue into KILLMODULE, below.
  269.  
  270.         ; Code to kill the module off.  Nothing here that you don't get in
  271.         ; other modules, the only point worthy of note is that we hang around
  272.         ; in this routine until all the characters have been read out of the
  273.         ; circular buffer.
  274.  
  275. KILLMODULE
  276.         LOCALS
  277.  
  278.         ; Restore environment
  279.  
  280.         MOV R0,#6 ; Error handler
  281.         ADR R1,OLDERRORHANDLER
  282.         LDMIA R1,{R1-R3}
  283.         SWI "OS_ChangeEnvironment"
  284.  
  285.         MOV R0,#11 ; Exit handler
  286.         ADR R1,OLDEXITHANDLER
  287.         LDMIA R1,{R1-R3}
  288.         SWI "OS_ChangeEnvironment"
  289.  
  290.         ; Write the return code into the message word, for the foreground
  291.         ; task
  292.  
  293.         LD R0,RETURNCODE
  294.         ORR R0,R0,#&80000000
  295.         ST R0,MESSAGES
  296.  
  297.         ; Now call Wimp_Poll until all the characters have been removed from
  298.         ; the buffer and the return code has been read
  299.  
  300. A
  301.         MOV R0,#0
  302.         ADR R1,POLLBLOCK
  303.         SWI "Wimp_Poll"
  304.         LD R0,OUTBUFFERREAD
  305.         LD R1,OUTBUFFERWRITE
  306.         TEQ R0,R1
  307.         BNE A
  308.         LD R0,MESSAGES
  309.         TEQ R0,#0
  310.         BNE A
  311.  
  312.         ; Finish off as a Wimp task
  313.  
  314.         LD R0,TASKHANDLE
  315.         LIT R1,&4B534154
  316.         SWI "Wimp_CloseDown"
  317.  
  318.         ; Release workspace
  319.  
  320.         MOV R0,#7
  321.         MOV R2,R12
  322.         SWI "OS_Module"
  323.  
  324.         ; Zero private word so RISC OS doesn't free it again!!
  325.  
  326.         LD R0,PRIVATEWORD
  327.         MOV R1,#0
  328.         STR R1,[R0]
  329.  
  330.         ; ExitAndDie to end program and kill module; don't set return code
  331.         ; as this was set by foreground task.
  332.  
  333.         MOV R0,#0
  334.         MOV R1,#0
  335.         MOV R2,#0
  336.         ADR R3,TITLE
  337.         SWI "OS_ExitAndDie"
  338.  
  339.         ; Just a standard service call handler.  Traps Service_Memory so we
  340.         ; get to keep the memory that the pre-empted task will run in!
  341.  
  342. SERVICE
  343.         ; Nothing to do here except veto attempts by the Wimp to remove all
  344.         ; our memory - this is tried once at the start.
  345.  
  346.         TEQ R1,#&11 ; Service_Memory
  347.         MOVNES PC,R14
  348.         STMFD R13!,{R0,R12,R14}
  349.         LDR R12,[R12]
  350.         LD R0,MEMORYMAPVETOED
  351.         TEQ R0,#0
  352.         LDMNEFD R13!,{R0,R12,PC}^
  353.         MOV R1,#0
  354.         MOV R0,#1
  355.         ST R0,MEMORYMAPVETOED
  356.         LDMFD R13!,{R0,R12,PC}^
  357.  
  358.         ; Claim write character (so we redirect the program's output into
  359.         ; the circular buffer) and open file (so we know what files the
  360.         ; program has open, so we can close them again if it is killed).
  361.  
  362. CLAIMVECS
  363.         MOV R0,#3 ; WrchV
  364.         ADR R1,WRCHV
  365.         MOV R2,R12
  366.         SWI "OS_Claim"
  367.  
  368.         MOV R0,#&D ; FindV
  369.         ADR R1,FINDV
  370.         MOV R2,R12
  371.         SWI "OS_Claim"
  372.  
  373.         MOVS PC,R14
  374.  
  375. RELEASEVECS
  376.         MOV R0,#3 ; WrchV
  377.         ADR R1,WRCHV
  378.         MOV R2,R12
  379.         SWI "OS_Release"
  380.  
  381.         MOV R0,#&D ; FindV
  382.         ADR R1,FINDV
  383.         MOV R2,R12
  384.         SWI "OS_Release"
  385.  
  386.         MOVS PC,R14
  387.  
  388.         ; Schedule a descheduling event.
  389.  
  390. SETCALLAFTER
  391.         MOV R0,#20
  392.         ADR R1,INTERRUPT
  393.         MOV R2,R12
  394.         SWI "OS_CallAfter"
  395.         MOVS PC,R14
  396.  
  397. CANCELTICKER
  398.         STMFD R13!,{R0,R1,R14}
  399.         ADR R0,INTERRUPT
  400.         MOV R1,R12
  401.         SWI "OS_RemoveTickerEvent"
  402.         LDMFD R13!,{R0,R1,PC}^
  403.  
  404. WRCHV
  405.         STMFD R13!,{R1-R2}
  406.         LD R1,OUTBUFFERREAD
  407.         LD R2,OUTBUFFERWRITE
  408.         ADD R2,R2,#2
  409.         BIC R2,R2,#&400
  410.         TEQ R1,R2
  411.         BLEQ CANCELTICKER
  412.         BLEQ SETCALLBACK
  413.         LD R1,OUTBUFFERWRITE
  414.         ADR R2,OUTBUFFER
  415.         STRB R0,[R1,R2]
  416.         ADD R1,R1,#1
  417.         BIC R1,R1,#&400
  418.         ST R1,OUTBUFFERWRITE
  419.         LDMFD R13!,{R1-R2}
  420.         LDMFD R13!,{PC}^
  421.  
  422. FINDV
  423.         LOCALS
  424.         TEQ R0,#0 ; Closing a file
  425.         BEQ A
  426.         STMFD R13!,{R12}
  427.         STMFD R13!,{PC}
  428.         MOVS PC,R14
  429.         MOV R0,R0 ; Our code is re-entered a word further down, so we waste
  430.                   ; this word.
  431.         LDMFD R13!,{R12}
  432.         LDMVSFD R13!,{PC}
  433.         STMFD R13!,{R2,R3}
  434.         ADR R2,HANDLES
  435.         LD R3,HANDLESUSED
  436.         TEQ R3,#32
  437.         MOVEQ R3,#31
  438.         STR R0,[R2,R3,LSL #2]
  439.         ADD R3,R3,#1
  440.         ST R3,HANDLESUSED
  441.         LDMFD R13!,{R2,R3}
  442.         LDMFD R13!,{PC}^
  443. A
  444.         TEQ R1,#0 ; Closing all files
  445.         BEQ B
  446.         STMFD R13!,{R2,R8,R9,R14}
  447.         LD R8,HANDLESUSED
  448.         ADR R9,HANDLES
  449. C
  450.         TEQ R8,#0
  451.         LDMEQFD R13!,{R2,R8,R9,PC}^
  452.         LDR R2,[R9],#4
  453.         SUB R8,R8,#1
  454.         TEQ R1,R2
  455.         BNE C
  456.         MOV R2,#0
  457.         STR R2,[R9,#-4]
  458.         LDMFD R13!,{R2,R8,R9,PC}^
  459. B
  460.         STMFD R13!,{R0,R14}
  461.         MOV R0,#0
  462.         ST R0,HANDLESUSED
  463.         LDMFD R13!,{R0,PC}^
  464.  
  465. INTERRUPT
  466.         ; Remember that we must not corrupt R14svc, so we have to mess about
  467.         ; with changing processor mode.
  468.  
  469.         STMFD R13!,{R0,R14}     ; R14irq goes onto irq stack.
  470.         MOVS R0,PC              ; Preserve old processor mode.
  471.         TEQP PC,#3
  472.         MOV R0,R0
  473.         STMFD R13!,{R14}
  474.         BL SETCALLBACK          ; Use CallBack code.
  475.         LDMFD R13!,{R14}
  476.         TEQP R0,#0
  477.         MOV R0,R0
  478.         LDMFD R13!,{R0,PC}^
  479.  
  480.         ; The following is just standard CallBack code, it restores the
  481.         ; handler address to its old value before returning to the main
  482.         ; program loop.
  483.  
  484. SETCALLBACK
  485.         STMFD R13!,{R0-R3,R14}
  486.         MOV R0,#7               ; CallBack
  487.         ADR R1,CALLBACKHANDLER
  488.         MOV R2,R12
  489.         ADR R3,REGBUF
  490.         SWI "OS_ChangeEnvironment"
  491.         ADR R14,OLDCALLBACK
  492.         STMIA R14,{R1-R3}
  493.         SWI "OS_SetCallBack"
  494.         LDMFD R13!,{R0-R3,PC}^
  495.  
  496. CALLBACKHANDLER
  497.         TEQP PC,#0
  498.         MOV R0,R0
  499.         MOV R0,#7
  500.         ADR R1,OLDCALLBACK
  501.         LDMIA R1,{R1-R3}
  502.         SWI "OS_ChangeEnvironment"
  503.         B MAINLOOP
  504.  
  505.         ; If we get an error reported, we write it into the circular buffer
  506.         ; and kill the program.
  507.  
  508. ERRORHANDLER
  509.         MOV R12,R0
  510.         MOV R0,#2
  511.         ST R0,RETURNCODE
  512.         SWI "OS_NewLine"
  513.         ADR R0,ERRORBUFFER+8
  514.         SWI "OS_Write0"
  515.         SWI "OS_NewLine"
  516.         ADR R13,USRSTACK
  517.         BL CANCELTICKER
  518.         BL RELEASEVECS
  519.         B KILLMODULE
  520.  
  521.         ; If the program exits, we tidy up.
  522.  
  523. EXITHANDLER
  524.         ST R2,RETURNCODE
  525.         ADR R13,USRSTACK
  526.         BL CANCELTICKER
  527.         BL RELEASEVECS
  528.         B KILLMODULE
  529.  
  530.         ; These messages control the pre-empted task.  They allow the
  531.         ; controlling process to stop and restart it, terminate it (which
  532.         ; invokes the program's exit handler) and kill it (which will never
  533.         ; reschedule it, and just closes all its files before shutting down).
  534.  
  535. RECEIVEMESSAGES
  536.         LOCALS
  537.         STMFD R13!,{R0,R14}
  538.         LD R0,MESSAGES
  539.         STMFD R13!,{R0}
  540.         TST R0,#&80000000
  541.         MOVEQ R0,#0
  542.         ST R0,MESSAGES
  543.         LDMFD R13!,{R0}
  544.         TEQ R0,#1 ; Suspend
  545.         BEQ A
  546.         TEQ R0,#2 ; Restart
  547.         BEQ B
  548.         TEQ R0,#3 ; Terminate
  549.         BEQ C
  550.         TEQ R0,#4 ; Kill
  551.         BEQ D
  552.         LDMFD R13!,{R0,PC}^
  553. A
  554.         MOV R0,#1
  555.         ST R0,SUSPENDED
  556.         LDMFD R13!,{R0,PC}^
  557. B
  558.         MOV R0,#0
  559.         ST R0,SUSPENDED
  560.         LDMFD R13!,{R0,PC}^
  561. C
  562.         MOV R0,#1
  563.         ST R0,TERMINATING
  564.         LDMFD R13!,{R0,PC}^
  565. D
  566.         MOV R9,#0
  567.         LD R10,HANDLESUSED
  568.         ADR R11,HANDLES
  569. E
  570.         TEQ R10,#0
  571.         BEQ F
  572.         MOV R0,#0
  573.         LDR R1,[R11],#4
  574.         TEQ R1,#0
  575.         ADDNE R9,R9,#1
  576.         SWINE "OS_Find"
  577.         SUB R10,R10,#1
  578.         B E
  579. F
  580.         BL CLAIMVECS
  581.         SWI "OS_WriteS"
  582.         DB 13,10,10,"Killed",13,10,0
  583.         ALIGN
  584.         MOV R0,R9
  585.         ADR R1,ERRORBUFFER
  586.         MOV R2,#16
  587.         SWI "OS_ConvertCardinal4"
  588.         SWI "OS_Write0"
  589.         SWI "OS_WriteS"
  590.         DB " file(s) closed",13,10,0
  591.         ALIGN
  592.         BL RELEASEVECS
  593.         MOV R0,#2
  594.         ST R0,RETURNCODE
  595.         B KILLMODULE
  596.  
  597.         DSECT
  598.         ORG &10000000
  599. WSTART
  600.  
  601.  
  602. PRIVATEWORD     ALLOC 4
  603. MESSAGES        ALLOC 4         ; Messages are written here by a controlling
  604.                                 ; task.
  605. OUTBUFFERREAD   ALLOC 4
  606. OUTBUFFERWRITE  ALLOC 4
  607. OUTBUFFER       ALLOC 1024      ; The output circular buffer
  608. TASKHANDLE      ALLOC 4
  609. MEMORYMAPVETOED ALLOC 4
  610. SUSPENDED       ALLOC 4
  611. TERMINATING     ALLOC 4
  612. HANDLESUSED     ALLOC 4
  613. RETURNCODE      ALLOC 4
  614. REGBUF          ALLOC 4*16
  615. OLDCALLBACK     ALLOC 4*3
  616. OLDERRORHANDLER ALLOC 4*3
  617. OLDEXITHANDLER  ALLOC 4*3
  618. HANDLES         ALLOC 4*32
  619. POLLBLOCK       ALLOC 256
  620. ERRORBUFFER     ALLOC 256+4+4
  621.  
  622.                 ALLOC 64
  623. USRSTACK
  624.  
  625.  
  626. WEND
  627.         DEND
  628.  
  629.         END
  630.  
  631.